package com.aorise.shiro;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @Auther: zhouhao
 * @Date: 2019/4/26
 * @Description:
 */
@Configuration
public class ShiroConfig {

    @Bean
    public JavaUuidSessionIdGenerator sessionIdGenerator(){
        return new JavaUuidSessionIdGenerator();
    }

    @Bean
    public SessionManager sessionManager(){
        MyShiroSessionManager mySessionManager = new MyShiroSessionManager();
        return mySessionManager;
    }

    /**
     *  配置SecurityManager
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm());  // 设置realm
        securityManager.setSessionManager(sessionManager());    // 设置sessionManager
        return securityManager;
    }

    /**
     *
     * 因为我们的密码是加过密的，所以，如果要Shiro验证用户身份的话，
     * 需要告诉它我们用的是md5加密的，并且是加密了10次。
     * 同时我们在自己的Realm中也通过SimpleAuthenticationInfo返回了加密时使用的盐。
     * 这样Shiro就能顺利的解密密码并验证用户名和密码是否正确了。
     * 配置Realm
     * 身份认证realm
     * @return
     */
    @Bean
    public PermissionRealm realm(){
        PermissionRealm realm = new PermissionRealm();
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        // 指定加密算法
        matcher.setHashAlgorithmName("MD5");
        // 指定加密次数
        matcher.setHashIterations(10);
        // 指定这个就不会报错
        matcher.setStoredCredentialsHexEncoded(true);
        realm.setCredentialsMatcher(matcher);
        return realm;
    }

    /**
     * 配置LifecycleBeanPostProcessor，可以来自动的调用配置在Spring IOC容器中 Shiro Bean 的生命周期方法
     * @return
     */
    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 启用IOC容器中使用Shiro的注解，但是必须配置第四步才可以使用
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        return new DefaultAdvisorAutoProxyCreator();
    }

    /**
     * 启用IOC容器中使用Shiro的注解
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     *  配置ShiroFilter
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        LinkedHashMap<String, String> map = new LinkedHashMap<>();

        // 公共路径
        map.put("/swagger-ui.html/**", "anon");
        map.put("/swagger-resources/**", "anon");
        map.put("/v2/**", "anon");
        map.put("/webjars/**", "anon");
        map.put("/api/admin/login", "anon");
        map.put("/api/admin/logout", "anon");
//        map.put("/api/user/register", "anon");
        map.put("/api/admin/notRole", "anon");
        map.put("/api/admin/notLogin", "anon");
//        暴露接口,供CMU调用
        map.put("/DevInfo", "anon");
        map.put("/DevState", "anon");
        map.put("/EventInfo", "anon");

        map.put("/api/organ/**", "anon");
        map.put("/api/camera/**", "anon");
        map.put("/api/sysConfig/**", "anon");
        map.put("/api/migration/**", "anon");
        map.put("/api/halt/**", "anon");
        map.put("/api/cameraGroup/**", "anon");
        map.put("/api/approve/**", "anon");
        map.put("/api/assess/**", "anon");
        map.put("/api/diagnosisPlan/**", "anon");
        map.put("/api/diagnosisReport/**", "anon");

        // 登出,项目中没有/logout路径,因为shiro是过滤器,而SpringMVC是Servlet,Shiro会先执行
        //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了
        map.put("/logout", "logout");

        // <!-- 过滤链定义，从上向下顺序执行，一般将/**放在最为下边 -->:这是一个坑呢，一不小心代码就不好使了;
        // authc:所有url都必须认证通过才可以访问;
        // anon:所有url都都可以匿名访问;
        // user:认证通过或者记住了登录状态(remeberMe)则可以通过
        map.put("/**", "authc");

        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        Map<String ,Filter> filters=factoryBean.getFilters();
        //将自定义 的RolesAnyAuthorizationFilter注入shiroFilter中
//        filters.put("authc", new RolesAnyAuthorizationFilter());
        filters.put("authc", new MyFormAuthenticationFilter());

        // 配置SecurityManager
        factoryBean.setSecurityManager(securityManager());
        // 配置权限路径
        factoryBean.setFilterChainDefinitionMap(map);
        // 配置登录url
        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        factoryBean.setLoginUrl("/api/admin/notLogin");
        // 登录成功后要跳转的链接
//        factoryBean.setSuccessUrl("/successful");
        // 配置无权限路径
        //未授权界面;
        //未授权跳转页面，但是用注解RequiresPermissions配置的权限不通过会抛出AuthorizationException异常，并不会跳转，需要做相应的异常处理
        factoryBean.setUnauthorizedUrl("/api/admin/notRole");
        return factoryBean;
    }
}
